package com.jl.driver.vritual.ternimal;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jl.util.HexUtil;

import opencard.core.terminal.CardID;
import opencard.core.terminal.CardTerminal;
import opencard.core.terminal.CardTerminalException;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.terminal.SlotChannel;

public class ComPortTernimal extends CardTerminal {
	
	public final static Logger log = LoggerFactory.getLogger(ComPortTernimal.class);

	private COM com;
	private String port;
	private CardID cid;
	private byte[] ATR;
	private boolean connected;
	private byte[] stack = new byte[300];
	/** time out */
	private int timeout = 1000;
	private int baudrate = 115200;

	private boolean isAllocate;

	protected ComPortTernimal(String arg0, String arg1, String arg2)
			throws CardTerminalException {
		super(arg0, arg1, arg2);
		port = arg2;
		addSlots(1);
		log.info("ComPortTernimal Construct called");
	}

	protected ComPortTernimal(String arg0, String arg1, String arg2,
			String baudrate, String timeout) throws CardTerminalException {
		this(arg0, arg1, arg2);
		this.timeout = Integer.parseInt(timeout);
		this.baudrate = Integer.parseInt(baudrate);
	}

	@Override
	public synchronized void close() throws CardTerminalException {
		log.info("COM  close");
		releaseComPort();
		connected = false;
		com = null;
		cid = null;
		ATR = null;
	}

	@Override
	public CardID getCardID(int arg0) throws CardTerminalException {
		if (!connected) {
			open();
		}
		if (!connected) {
			throw new CardTerminalException(
					"JCOPSimCardTerminal: getCardID(), Terminal not opened.");
		}
		return cid;
	}
	
	@Override
	protected void internalOpenSlotChannel(int slotID)
			throws CardTerminalException {
		super.internalOpenSlotChannel(slotID);
		try {
			allocateComPort();
		} catch (COMException e) {
			throw new CardTerminalException(e.getMessage());
		}
	}
	
	@Override
	protected void internalCloseSlotChannel(SlotChannel arg0)
			throws CardTerminalException {
		super.internalCloseSlotChannel(arg0);
		releaseComPort();
	}

	@Override
	protected CardID internalReset(int arg0, int arg1)
			throws CardTerminalException {
		close();
		open();
		try {
			allocateComPort();
			fetchATR();
		} catch (COMException e) {
			throw new CardTerminalException(e.getMessage());
		}
		if(ATR == null){
			throw new CardTerminalException("Reset failed !");
		}
		cid = new CardID(ATR);
		cardInserted(0);
		return cid;
	}

	@Override
	protected ResponseAPDU internalSendAPDU(int arg0, CommandAPDU capdu,
			int arg2) throws CardTerminalException {
		if (!connected) {
			open();
		}
		if (!connected) {
			throw new CardTerminalException(
					"ComCardException : Error sending APDU! No connection");
		}
		ResponseAPDU r = null;
		try {
			byte[] apdu = capdu.getBytes();
			byte[] rsp = T0Send(apdu);
			r = new ResponseAPDU(rsp);
		} catch (Exception e) {
			throw new CardTerminalException(
					"ComCardException: Error sending APDU! Reason: "
							+ e.getLocalizedMessage());
		}
		return r;
	}

	@Override
	public boolean isCardPresent(int arg0) throws CardTerminalException {
		if (!connected) {
			open();
		}
		return connected;
	}

	@Override
	public synchronized void open() throws CardTerminalException {
		log.info("COM  open");
		if (connected) {
			return;
		}
		if (!checkPort()) {
			connected = false;
			return;
		}
		connected = true;
	}

	/**
	 * check the port is exist or not.
	 * 
	 * @return exist then return true else return false.
	 */
	private boolean checkPort() {
		for (String one : COM.listPorts()) {
			if (one.equals(port)) {
				return true;
			}
		}
		return false;
	}

	private void fetchATR() throws COMException {
		ATR = null;
		allocateComPort();
		byte[] buf = new byte[32];
		int len;
		int offset = 0;
		if (com.read(timeout, buf, 0, 2) != 0)
			return;
		int l_H = buf[1] & 0x0F;
		offset = 2;
		boolean needTCK = false;
		while (true) {
			int bmp = buf[offset - 1] & 0xF0;
			len = 4;
			switch (bmp) {
			case 0x00:
				len--;
			case 0x10:
			case 0x20:
			case 0x40:
			case 0x80:
				len--;
			case 0x30:
			case 0x50:
			case 0x60:
			case 0x90:
			case 0xA0:
			case 0xC0:
				len--;
			case 0x70:
			case 0xB0:
			case 0xD0:
			case 0xE0:
				len--;
			case 0xF0:
				break;
			}
			if (len == 0)
				break;

			if (com.read(buf, offset, len) != 0)
				return;
			offset += len;
			if ((bmp & 0x80) == 0)
				break;
			if ((buf[offset - 1] & 0x0F) != 0)
				needTCK = true;

		}
		len = l_H;
		if (needTCK)
			len++;
		if (len > 0) {
			if (com.read(buf, offset, len) != 0)
				return;
		}
		len += offset;
		ATR = new byte[len];
		System.arraycopy(buf, 0, ATR, 0, len);
	}

	private byte[] T0Send(byte[] buf) throws COMException {
		byte[] head = new byte[5];
		int length = 0;
		if (buf.length < 4)
			COMException.throwIt("APDU Format Error: "
					+ HexUtil.toHexString(head));
		System.arraycopy(buf, 0, head, 0, 4);
		if (buf.length > 4)
			head[4] = buf[4];
		com.write(head, 0, 5);
		toGetProcedureByte();
		if (stack[0] == head[1]) {
			if (buf.length > 5) {
				com.write(buf, 5, buf[4] & 0xFF);
				toGetProcedureByte();
				length = 2;
				// System.out.println("P->" + HexUtil.toHexString(stack, 0,
				// 1));
				if (com.read(stack, 1, length - 1) != 0)
					COMException.throwIt("APDU get response error: "
							+ HexUtil.toHexString(buf));
			} else {
				length = head[4] & 0xFF;
				if (length == 0)
					length = 0x100;
				length += 2;
				if (com.read(stack, 0, length) != 0)
					COMException.throwIt("APDU get response error: "
							+ HexUtil.toHexString(buf));
			}
		} else {
			length = 2;
			if (com.read(stack, 1, length - 1) != 0)
				COMException.throwIt("APDU get response error: "
						+ HexUtil.toHexString(buf));
		}
		buf = new byte[length];
		System.arraycopy(stack, 0, buf, 0, length);
		return buf;
	}

	private void toGetProcedureByte() throws COMException {
		do {
			if (com.read(stack, 0, 1) != 0)
				COMException.throwIt("APDU get response error: TIMEOUT.");
		} while (stack[0] == (byte) 0x60);
	}

	private static void delay(final int ms) {
		try {
			Thread.sleep(ms);
		} catch (Exception ex) {

		}
	}
	
	private void allocateComPort() throws COMException{
		if(isAllocate){
			return;
		}
		com = new COM(port);
		com.open(baudrate, COM.DATABITS_8, COM.STOPBITS_2, COM.PARITY_EVEN,
				false, false);
		this.isAllocate = true;
	}
	
	private void releaseComPort(){
		if(!isAllocate){
			return;
		}
		com.close();
		this.isAllocate = false;
	}

}
